package repository

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"mall/common/globalkey"
	"mall/common/modelx"
	"mall/service/forum/model"

	"github.com/sirupsen/logrus"
	"github.com/volatiletech/null/v8"
	"github.com/volatiletech/sqlboiler/v4/boil"
	"github.com/volatiletech/sqlboiler/v4/queries"
	"github.com/volatiletech/sqlboiler/v4/queries/qm"
	"github.com/zeromicro/go-zero/core/stores/cache"
)

type (
	// TopicRepository is an interface to be customized, add more methods here,
	// and implement the added methods in customTopicRepository.
	TopicRepository interface {
		FindOne(ctx context.Context, id uint64) (*model.ForumTopic, error)
		FindOneWithExpire(
			ctx context.Context,
			id uint64,
			expire time.Duration,
		) (*model.ForumTopic, error)
		FindOneNoCache(
			ctx context.Context,
			conn boil.ContextExecutor,
			id uint64,
		) (*model.ForumTopic, error)
		GetList(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ForumTopic, int64, error)
		Insert(
			ctx context.Context,
			tx boil.ContextExecutor,
			data *model.ForumTopic,
			rel *model.ForumTopicRel,
		) error
		Update(
			ctx context.Context,
			tx boil.ContextExecutor,
			data *model.ForumTopic,
			rel *model.ForumTopicRel,
		) error
		UpdateBatch(
			ctx context.Context,
			tx boil.ContextExecutor,
			ids []uint64,
			cols model.M,
			rel *model.ForumTopicRel,
		) error
		SoftDelete(ctx context.Context, tx boil.ContextExecutor, id uint64) error
		Restore(ctx context.Context, tx boil.ContextExecutor, id uint64) error
		GetMedia(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ForumAttachmentRelationship, int64, error)
		GetMeta(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ForumTopicMetum, int64, error)
		UpdateMeta(
			ctx context.Context,
			tx boil.ContextExecutor,
			data *model.ForumTopicMetum,
		) (err error)
		UpdateMediaBatch(
			ctx context.Context,
			tx boil.ContextExecutor,
			ids []uint64,
			cols model.M,
		) (int64, error)
		IncreaseMeta(
			ctx context.Context,
			tx boil.ContextExecutor,
			topicId uint64,
			metaKey string,
			step int,
		) error
		GetReplyList(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ForumPost, int64, error)
	}

	customTopicRepository struct {
		modelx.CachedConn
		mysqlConn           *sql.DB
		cacheIdPrefix       string
		cacheIdExpirePrefix string
	}
)

// NewTopicRepository returns a repository for the database table.
func NewTopicRepository(mysqlConn *sql.DB, cacheConf cache.CacheConf) TopicRepository {
	return &customTopicRepository{
		CachedConn:          modelx.NewConn(mysqlConn, cacheConf),
		mysqlConn:           mysqlConn,
		cacheIdPrefix:       "cache:forum:topics:id:",
		cacheIdExpirePrefix: "cache:forum:topics:id:expire:",
	}
}

func (r *customTopicRepository) FindOne(
	ctx context.Context,
	id uint64,
) (*model.ForumTopic, error) {
	cacheIdKey := r.formatPrimary(id)
	var resp model.ForumTopic
	err := r.QueryCtx(ctx, &resp, cacheIdKey, func(ctx context.Context, conn *sql.DB, v any) error {
		data, err := model.FindForumTopic(ctx, conn, id)
		if err != nil {
			return err
		}

		*v.(*model.ForumTopic) = *data

		return nil
	})

	switch err {
	case nil:
		return &resp, nil
	case sql.ErrNoRows:
		return nil, model.ErrNotFound
	default:
		return nil, err
	}
}

func (r *customTopicRepository) FindOneWithExpire(
	ctx context.Context,
	id uint64,
	expire time.Duration,
) (*model.ForumTopic, error) {
	cacheIdKey := r.formatPrimary(id)
	var resp model.ForumTopic
	err := r.QueryWithExpireCtx(ctx, &resp, cacheIdKey, expire, func(ctx context.Context, conn *sql.DB, v any) error {
		data, err := model.FindForumTopic(ctx, conn, id)
		if err != nil {
			return err
		}

		*v.(*model.ForumTopic) = *data

		return nil
	})

	switch err {
	case nil:
		return &resp, nil
	case sql.ErrNoRows:
		return nil, model.ErrNotFound
	default:
		return nil, err
	}
}

func (r *customTopicRepository) FindOneNoCache(
	ctx context.Context,
	conn boil.ContextExecutor,
	id uint64,
) (*model.ForumTopic, error) {
	var db boil.ContextExecutor = r.mysqlConn
	if conn != nil {
		db = conn
	}
	data, err := model.FindForumTopic(ctx, db, id)
	logrus.Info(fmt.Sprintf("FindOneNoCache err: %+v", err))
	switch err {
	case nil:
		return data, nil
	case sql.ErrNoRows:
		return nil, model.ErrNotFound
	default:
		return nil, err
	}
}

func (r *customTopicRepository) GetList(
	ctx context.Context,
	cols model.M,
	withCount int, // -1-返回list；0-返回count；1-返回count和list；2-根据offset返回count和list；3-根据preMinId返回count和list；4-根据preMaxId返回count和list
	filter map[string]map[string][]any,
) (list []*model.ForumTopic, count int64, err error) {
	var page int
	var pageSize int
	var limit int
	var selectCol string
	var mods []qm.QueryMod
	var nextCount int64
	var previousCount int64
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(uint64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(uint64); ok && val > 0 {
				pageSize = int(val)
			}
		case "limit":
			if val, ok := v.(uint32); ok && val > 0 {
				limit = int(val)
			}
		case "offset":
			if withCount == 2 {
				if val, ok := v.(uint32); ok && val > 0 {
					mods = append(mods, qm.Offset(int(val)))
					previousCount = 1
				}
			}
		case "preMinId":
			if withCount == 3 {
				if val, ok := v.(uint64); ok && val > 0 {
					mods = append(mods, qm.OrderBy(model.ForumTopicColumns.TopicID+" DESC"))
					mods = append(mods, model.ForumTopicWhere.TopicID.LT(val))
					previousCount = 1
				}
			}
		case "preMaxId":
			if withCount == 4 {
				if val, ok := v.(uint64); ok && val > 0 {
					mods = append(mods, qm.OrderBy(model.ForumTopicColumns.TopicID+" ASC"))
					mods = append(mods, model.ForumTopicWhere.TopicID.GT(val))
					previousCount = 1
				}
			}
		case "orderBy":
			if withCount != 3 && withCount != 4 {
				if val, ok := v.(string); ok && val != "" {
					mods = append(mods, qm.OrderBy(val))
				}
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	for key, opMap := range filter {
		switch key {
		case model.ForumTopicColumns.TopicID:
			mods = append(mods, modelx.GetIntMods(model.ForumTopicColumns.TopicID, opMap)...)
		case model.ForumTopicColumns.ForumID:
			mods = append(mods, modelx.GetIntMods(model.ForumTopicColumns.ForumID, opMap)...)
		case model.ForumTopicColumns.TopicAuthorID:
			mods = append(mods, modelx.GetIntMods(model.ForumTopicColumns.TopicAuthorID, opMap)...)
		case model.ForumTopicColumns.TopicAuthorNickname:
			mods = append(mods, modelx.GetStringMods(model.ForumTopicColumns.TopicAuthorNickname, opMap)...)
		case model.ForumTopicColumns.TopicAuthorRankID:
			mods = append(mods, modelx.GetIntMods(model.ForumTopicColumns.TopicAuthorRankID, opMap)...)
		case model.ForumTopicColumns.TopicAuthorAnonymous:
			mods = append(mods, modelx.GetIntMods(model.ForumTopicColumns.TopicAuthorAnonymous, opMap)...)
		case model.ForumTopicColumns.Title:
			mods = append(mods, modelx.GetStringMods(model.ForumTopicColumns.Title, opMap)...)
		case model.ForumTopicColumns.Slug:
			mods = append(mods, modelx.GetStringMods(model.ForumTopicColumns.Slug, opMap)...)
		case model.ForumTopicColumns.TopicStatus:
			mods = append(mods, modelx.GetIntMods(model.ForumTopicColumns.TopicStatus, opMap)...)
		case model.ForumTopicColumns.Sticky:
			mods = append(mods, modelx.GetIntMods(model.ForumTopicColumns.Sticky, opMap)...)
		case model.ForumTopicColumns.PostCount:
			mods = append(mods, modelx.GetIntMods(model.ForumTopicColumns.PostCount, opMap)...)
		case model.ForumTopicColumns.ViewCount:
			mods = append(mods, modelx.GetIntMods(model.ForumTopicColumns.ViewCount, opMap)...)
		case model.ForumTopicColumns.CommentCount:
			mods = append(mods, modelx.GetIntMods(model.ForumTopicColumns.CommentCount, opMap)...)
		}
	}

	if withCount >= 2 {
		if limit > 0 {
			mods = append(mods, qm.Limit(limit+1))
		}
	} else if withCount >= 0 {
		count, err = model.ForumTopics(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}

		if pageSize > 0 {
			if page > 1 {
				mods = append(mods, qm.Offset(pageSize*(page-1)))
			}
			mods = append(mods, qm.Limit(pageSize))
		}
	} else {
		if pageSize > 0 {
			if page > 1 {
				mods = append(mods, qm.Offset(pageSize*(page-1)))
			}
			mods = append(mods, qm.Limit(pageSize))
		}
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		} else {
			columns = append(columns, model.ForumTopicColumns.TopicID)
		}
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ForumTopics(mods...).All(ctx, r.mysqlConn)
	if withCount >= 2 {
		if limit > 0 && len(list) > limit {
			nextCount = 1
			list = list[:limit]
		}
		count = previousCount + nextCount*2
	}

	return
}

func (r *customTopicRepository) Insert(
	ctx context.Context,
	tx boil.ContextExecutor,
	data *model.ForumTopic,
	rel *model.ForumTopicRel,
) error {
	err := r.ExecCtx(ctx, func(ctx context.Context, conn *sql.DB) error {
		var db boil.ContextExecutor
		if tx != nil {
			db = tx
		} else {
			db = conn
		}

		err := data.Insert(ctx, db, boil.Infer())
		if err != nil {
			return err
		}

		if rel == nil {
			return nil
		}

		topicId := data.TopicID
		if len(rel.Meta) > 0 {
			for _, v := range rel.Meta {
				v.TopicID = topicId
				err = v.Insert(ctx, db, boil.Infer())
				if err != nil {
					return err
				}
			}
		}

		if len(rel.Media) > 0 {
			for _, v := range rel.Media {
				v.ObjectID = topicId
				v.ObjectType = uint16(globalkey.AttachmentLogTypeForumTopic)
				err = v.Insert(ctx, db, boil.Infer())
				if err != nil {
					return err
				}
			}
		}

		if rel.Feed != nil {
			rel.Feed.TopicID = topicId
			err = rel.Feed.Insert(ctx, db, boil.Infer())
			if err != nil {
				return err
			}
		}

		if rel.Post != nil {
			rel.Post.TopicID = topicId
			err = rel.Post.Insert(ctx, db, boil.Infer())
			if err != nil {
				return err
			}
		}

		return nil
	}, r.getCacheKeys(data)...)

	return err
}

func (r *customTopicRepository) Update(
	ctx context.Context,
	tx boil.ContextExecutor,
	data *model.ForumTopic,
	rel *model.ForumTopicRel,
) error {
	err := r.ExecCtx(ctx, func(ctx context.Context, conn *sql.DB) error {
		var db boil.ContextExecutor
		if tx != nil {
			db = tx
		} else {
			db = conn
		}

		rowsAff, err := data.Update(ctx, db, boil.Blacklist(
			model.ForumTopicColumns.TopicID,
			model.ForumTopicColumns.ForumID,
			model.ForumTopicColumns.TopicAuthorID,
			model.ForumTopicColumns.TopicDate,
			model.ForumTopicColumns.TopicDateGMT,
			model.ForumTopicColumns.DeletedAt,
		))
		if err != nil {
			return err
		}

		if rowsAff == 0 {
			return model.ErrNotUpdated
		}

		if rel == nil {
			return nil
		}

		topicId := data.TopicID
		if len(rel.Meta) > 0 {
			for _, v := range rel.Meta {
				if v.MetaID > 0 {
					_, err = v.Update(ctx, db, boil.Blacklist(
						model.ForumTopicMetumColumns.MetaID,
						model.ForumTopicMetumColumns.TopicID,
						model.ForumTopicMetumColumns.MetaKey,
					))
					if err != nil {
						return err
					}
				} else {
					v.TopicID = topicId
					err = v.Insert(ctx, db, boil.Infer())
					if err != nil {
						return err
					}
				}
			}
		}

		if len(rel.Media) > 0 {
			for _, v := range rel.Media {
				if v.AttachmentRelationshipID > 0 {
					_, err = v.Update(ctx, db, boil.Blacklist(
						model.ForumAttachmentRelationshipColumns.AttachmentRelationshipID,
						model.ForumAttachmentRelationshipColumns.ObjectID,
						model.ForumAttachmentRelationshipColumns.ObjectType,
						model.ForumAttachmentRelationshipColumns.AttachmentID,
					))
					if err != nil {
						return err
					}
				} else {
					v.ObjectID = topicId
					v.ObjectType = uint16(globalkey.AttachmentLogTypeForumTopic)
					err = v.Insert(ctx, db, boil.Infer())
					if err != nil {
						return err
					}
				}
			}
		}

		if len(rel.DelMetaIds) > 0 {
			var mods []qm.QueryMod
			mods = append(mods, model.ForumTopicMetumWhere.MetaID.IN(rel.DelMetaIds))
			_, err = model.ForumTopicMeta(mods...).DeleteAll(ctx, db)
			if err != nil {
				return err
			}
		}

		if len(rel.DelMediaIds) > 0 {
			var mods []qm.QueryMod
			mods = append(mods, model.ForumAttachmentRelationshipWhere.AttachmentRelationshipID.IN(rel.DelMediaIds))
			_, err = model.ForumAttachmentRelationships(mods...).DeleteAll(ctx, db)
			if err != nil {
				return err
			}
		}

		return nil
	}, r.getCacheKeys(data)...)

	return err
}

func (r *customTopicRepository) UpdateBatch(
	ctx context.Context,
	tx boil.ContextExecutor,
	ids []uint64,
	cols model.M,
	rel *model.ForumTopicRel,
) error {
	if len(ids) > 0 {
		err := r.ExecCtx(ctx, func(ctx context.Context, conn *sql.DB) error {
			var db boil.ContextExecutor
			if tx != nil {
				db = tx
			} else {
				db = conn
			}

			var mods []qm.QueryMod
			mods = append(mods, model.ForumTopicWhere.TopicID.IN(ids))

			rowsAff, err := model.ForumTopics(mods...).UpdateAll(ctx, db, cols)
			if err != nil {
				return err
			}

			if rowsAff == 0 {
				return model.ErrNotUpdated
			}

			if len(rel.Logs) > 0 {
				for _, v := range rel.Logs {
					err = v.Insert(ctx, db, boil.Infer())
					if err != nil {
						return err
					}
				}
			}

			if len(rel.Notices) > 0 {
				for _, v := range rel.Notices {
					err = v.Insert(ctx, db, boil.Infer())
					if err != nil {
						return err
					}
				}
			}

			return nil
		}, r.formatPrimaries(ids)...)

		return err
	}

	return nil
}

func (r *customTopicRepository) SoftDelete(
	ctx context.Context,
	tx boil.ContextExecutor,
	id uint64,
) error {
	data, err := r.FindOneNoCache(ctx, tx, id)
	if err != nil {
		if err == model.ErrNotFound {
			return nil
		}

		return err
	}

	if data.TopicStatus == uint8(globalkey.StatusDeleted) {
		return nil
	}

	data.DelStatus = data.TopicStatus
	data.TopicStatus = uint8(globalkey.StatusDeleted)
	data.DeletedAt = null.TimeFrom(time.Now())

	err = r.ExecCtx(ctx, func(ctx context.Context, conn *sql.DB) error {
		var db boil.ContextExecutor
		if tx != nil {
			db = tx
		} else {
			db = conn
		}

		rowsAff, err := data.Update(ctx, db, boil.Blacklist(
			model.ForumTopicColumns.TopicID,
			model.ForumTopicColumns.TopicModifiedDate,
			model.ForumTopicColumns.TopicModifiedGMT,
		))
		if err != nil {
			return err
		}

		if rowsAff == 0 {
			return model.ErrNotUpdated
		}

		return nil
	}, r.getCacheKeys(data)...)

	return err
}

func (r *customTopicRepository) Restore(
	ctx context.Context,
	tx boil.ContextExecutor,
	id uint64,
) error {
	data, err := r.FindOneNoCache(ctx, tx, id)
	if err != nil {
		if err == model.ErrNotFound {
			return nil
		}

		return err
	}

	if data.TopicStatus != uint8(globalkey.StatusDeleted) {
		return nil
	}

	data.TopicStatus = data.DelStatus
	data.DeletedAt = null.TimeFromPtr(nil)

	err = r.ExecCtx(ctx, func(ctx context.Context, conn *sql.DB) error {
		var db boil.ContextExecutor
		if tx != nil {
			db = tx
		} else {
			db = conn
		}

		rowsAff, err := data.Update(ctx, db, boil.Blacklist(
			model.ForumTopicColumns.TopicID,
			model.ForumTopicColumns.TopicModifiedDate,
			model.ForumTopicColumns.TopicModifiedGMT,
		))
		if err != nil {
			return err
		}

		if rowsAff == 0 {
			return model.ErrNotUpdated
		}

		return nil
	}, r.getCacheKeys(data)...)

	return err
}

func (r *customTopicRepository) getCacheKeys(data *model.ForumTopic) []string {
	if data == nil {
		return []string{}
	}

	cacheIdKey := r.formatPrimary(data.TopicID)
	cacheKeys := []string{
		cacheIdKey,
	}

	return cacheKeys
}

func (r *customTopicRepository) formatPrimary(primary any) string {
	return fmt.Sprintf("%s%v", r.cacheIdPrefix, primary)
}

func (r *customTopicRepository) formatPrimaries(primaries []uint64) []string {
	var resp []string
	for _, primary := range primaries {
		resp = append(resp, r.formatPrimary(primary))
	}

	return resp
}

func (r *customTopicRepository) queryPrimary(ctx context.Context, conn *sql.DB, v, primary any) error {
	var pid uint64
	if jsonId, ok := primary.(json.Number); ok {
		if int64Id, err := jsonId.Int64(); err == nil {
			pid = uint64(int64Id)
		} else {
			return err
		}
	}

	data, err := model.FindForumTopic(ctx, conn, pid)
	if err != nil {
		return err
	}

	*v.(*model.ForumTopic) = *data

	return nil
}

func (r *customTopicRepository) GetMedia(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ForumAttachmentRelationship, count int64, err error) {
	var page int
	var pageSize int
	var limit int
	var selectCol string
	var mods []qm.QueryMod
	var nextCount int64
	var previousCount int64
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(uint64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(uint64); ok && val > 0 {
				pageSize = int(val)
			}
		case "limit":
			if val, ok := v.(uint32); ok && val > 0 {
				limit = int(val)
			}
		case "offset":
			if withCount == 2 {
				if val, ok := v.(uint32); ok && val > 0 {
					mods = append(mods, qm.Offset(int(val)))
					previousCount = 1
				}
			}
		case "preMinId":
			if withCount == 3 {
				if val, ok := v.(uint64); ok && val > 0 {
					mods = append(mods, qm.OrderBy(model.ForumAttachmentRelationshipColumns.AttachmentRelationshipID+" DESC"))
					mods = append(mods, model.ForumAttachmentRelationshipWhere.AttachmentRelationshipID.LT(val))
					previousCount = 1
				}
			}
		case "preMaxId":
			if withCount == 4 {
				if val, ok := v.(uint64); ok && val > 0 {
					mods = append(mods, qm.OrderBy(model.ForumAttachmentRelationshipColumns.AttachmentRelationshipID+" ASC"))
					mods = append(mods, model.ForumAttachmentRelationshipWhere.AttachmentRelationshipID.GT(val))
					previousCount = 1
				}
			}
		case "orderBy":
			if withCount != 3 && withCount != 4 {
				if val, ok := v.(string); ok && val != "" {
					mods = append(mods, qm.OrderBy(val))
				}
			}
		case "select":
			if withCount != 5 {
				if val, ok := v.(string); ok && val != "" {
					selectCol = val
				}
			}
		}
	}

	for key, opMap := range filter {
		switch key {
		case model.ForumAttachmentRelationshipColumns.AttachmentRelationshipID:
			mods = append(mods, modelx.GetIntMods(model.ForumAttachmentRelationshipColumns.AttachmentRelationshipID, opMap)...)
		case model.ForumAttachmentRelationshipColumns.ObjectID:
			mods = append(mods, modelx.GetIntMods(model.ForumAttachmentRelationshipColumns.ObjectID, opMap)...)
		case model.ForumAttachmentRelationshipColumns.ObjectType:
			mods = append(mods, modelx.GetIntMods(model.ForumAttachmentRelationshipColumns.ObjectType, opMap)...)
		case model.ForumAttachmentRelationshipColumns.AttachmentID:
			mods = append(mods, modelx.GetIntMods(model.ForumAttachmentRelationshipColumns.AttachmentID, opMap)...)
		case model.ForumAttachmentRelationshipColumns.AttachmentThumbnail:
			mods = append(mods, modelx.GetIntMods(model.ForumAttachmentRelationshipColumns.AttachmentThumbnail, opMap)...)
		case model.ForumAttachmentRelationshipColumns.AttachmentOrder:
			mods = append(mods, modelx.GetIntMods(model.ForumAttachmentRelationshipColumns.AttachmentOrder, opMap)...)
		case model.ForumAttachmentRelationshipColumns.AttachmentSource:
			mods = append(mods, modelx.GetStringMods(model.ForumAttachmentRelationshipColumns.AttachmentSource, opMap)...)
		case model.ForumAttachmentRelationshipColumns.AttachmentImageURL:
			mods = append(mods, modelx.GetStringMods(model.ForumAttachmentRelationshipColumns.AttachmentImageURL, opMap)...)
		case model.ForumAttachmentRelationshipColumns.AttachmentImageAlt:
			mods = append(mods, modelx.GetStringMods(model.ForumAttachmentRelationshipColumns.AttachmentImageAlt, opMap)...)
		}
	}

	if withCount >= 2 {
		if limit > 0 {
			mods = append(mods, qm.Limit(limit+1))
		}
	} else if withCount >= 0 {
		count, err = model.ForumAttachmentRelationships(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}

		if pageSize > 0 {
			if page > 1 {
				mods = append(mods, qm.Offset(pageSize*(page-1)))
			}
			mods = append(mods, qm.Limit(pageSize))
		}
	} else {
		if pageSize > 0 {
			if page > 1 {
				mods = append(mods, qm.Offset(pageSize*(page-1)))
			}
			mods = append(mods, qm.Limit(pageSize))
		}
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		} else {
			columns = append(columns, model.ForumAttachmentRelationshipColumns.AttachmentRelationshipID)
		}
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ForumAttachmentRelationships(mods...).All(ctx, r.mysqlConn)
	if withCount >= 2 {
		if limit > 0 && len(list) > limit {
			nextCount = 1
			list = list[:limit]
		}
		count = previousCount + nextCount*2
	}

	return
}

func (r *customTopicRepository) GetMeta(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ForumTopicMetum, count int64, err error) {
	var page int
	var pageSize int
	var limit int
	var selectCol string
	var mods []qm.QueryMod
	var nextCount int64
	var previousCount int64
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(uint64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(uint64); ok && val > 0 {
				pageSize = int(val)
			}
		case "limit":
			if val, ok := v.(uint32); ok && val > 0 {
				limit = int(val)
			}
		case "offset":
			if withCount == 2 {
				if val, ok := v.(uint32); ok && val > 0 {
					mods = append(mods, qm.Offset(int(val)))
					previousCount = 1
				}
			}
		case "preMinId":
			if withCount == 3 {
				if val, ok := v.(uint64); ok && val > 0 {
					mods = append(mods, qm.OrderBy(model.ForumTopicMetumColumns.MetaID+" DESC"))
					mods = append(mods, model.ForumTopicMetumWhere.MetaID.LT(val))
					previousCount = 1
				}
			}
		case "preMaxId":
			if withCount == 4 {
				if val, ok := v.(uint64); ok && val > 0 {
					mods = append(mods, qm.OrderBy(model.ForumTopicMetumColumns.MetaID+" ASC"))
					mods = append(mods, model.ForumTopicMetumWhere.MetaID.GT(val))
					previousCount = 1
				}
			}
		case "orderBy":
			if withCount != 3 && withCount != 4 {
				if val, ok := v.(string); ok && val != "" {
					mods = append(mods, qm.OrderBy(val))
				}
			}
		case "select":
			if withCount != 5 {
				if val, ok := v.(string); ok && val != "" {
					selectCol = val
				}
			}
		}
	}

	for key, opMap := range filter {
		switch key {
		case model.ForumTopicMetumColumns.MetaID:
			mods = append(mods, modelx.GetIntMods(model.ForumTopicMetumColumns.MetaID, opMap)...)
		case model.ForumTopicMetumColumns.TopicID:
			mods = append(mods, modelx.GetIntMods(model.ForumTopicMetumColumns.TopicID, opMap)...)
		case model.ForumTopicMetumColumns.MetaKey:
			mods = append(mods, modelx.GetStringMods(model.ForumTopicMetumColumns.MetaKey, opMap)...)
		}
	}

	if withCount >= 2 {
		if limit > 0 {
			mods = append(mods, qm.Limit(limit+1))
		}
	} else if withCount >= 0 {
		count, err = model.ForumTopicMeta(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}

		if pageSize > 0 {
			if page > 1 {
				mods = append(mods, qm.Offset(pageSize*(page-1)))
			}
			mods = append(mods, qm.Limit(pageSize))
		}
	} else {
		if pageSize > 0 {
			if page > 1 {
				mods = append(mods, qm.Offset(pageSize*(page-1)))
			}
			mods = append(mods, qm.Limit(pageSize))
		}
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		} else {
			columns = append(columns, model.ForumTopicMetumColumns.MetaID)
		}
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ForumTopicMeta(mods...).All(ctx, r.mysqlConn)
	if withCount >= 2 {
		if limit > 0 && len(list) > limit {
			nextCount = 1
			list = list[:limit]
		}
		count = previousCount + nextCount*2
	}

	return
}

func (r *customTopicRepository) UpdateMeta(
	ctx context.Context,
	tx boil.ContextExecutor,
	data *model.ForumTopicMetum,
) (err error) {
	err = r.ExecCtx(ctx, func(ctx context.Context, conn *sql.DB) error {
		var db boil.ContextExecutor
		if tx != nil {
			db = tx
		} else {
			db = conn
		}

		rowsAff, err := data.Update(ctx, db, boil.Blacklist(
			model.ForumTopicMetumColumns.TopicID,
			model.ForumTopicMetumColumns.MetaKey,
		))
		if err != nil {
			return err
		}

		if rowsAff == 0 {
			return model.ErrNotUpdated
		}
		return nil
	}, r.getCacheKeys(&model.ForumTopic{TopicID: data.TopicID})...)

	return nil
}

func (r *customTopicRepository) UpdateMediaBatch(
	ctx context.Context,
	tx boil.ContextExecutor,
	ids []uint64,
	cols model.M,
) (rowsAff int64, err error) {
	if len(ids) > 0 {
		var db boil.ContextExecutor
		if tx != nil {
			db = tx
		} else {
			db = r.mysqlConn
		}

		var mods []qm.QueryMod
		mods = append(mods, model.ForumAttachmentRelationshipWhere.AttachmentRelationshipID.IN(ids))
		rowsAff, err = model.ForumAttachmentRelationships(mods...).UpdateAll(ctx, db, cols)
	}

	return
}

func (r *customTopicRepository) IncreaseMeta(
	ctx context.Context,
	tx boil.ContextExecutor,
	topicId uint64,
	metaKey string,
	step int,
) error {
	if topicId == 0 {
		return nil
	}

	var db boil.ContextExecutor
	if tx != nil {
		db = tx
	} else {
		db = r.mysqlConn
	}

	var mods []qm.QueryMod
	mods = append(mods, model.ForumTopicMetumWhere.TopicID.EQ(topicId))
	mods = append(mods, model.ForumTopicMetumWhere.MetaKey.EQ(metaKey))
	list, err := model.ForumTopicMeta(mods...).All(ctx, db)
	if err != nil {
		return err
	}

	if len(list) == 0 {
		metum := &model.ForumTopicMetum{
			TopicID:   topicId,
			MetaKey:   metaKey,
			MetaValue: null.StringFrom(strconv.FormatInt(int64(step), 10)),
		}

		return metum.Insert(ctx, db, boil.Infer())
	}

	var info RandomInfo
	err = queries.Raw(fmt.Sprintf(
		`update %s set %s=%s+%d where %s="%s" and %s="%d"`,
		model.TableNames.ForumTopicMeta,
		model.ForumTopicMetumColumns.MetaValue,
		model.ForumTopicMetumColumns.MetaValue,
		step,
		model.ForumTopicMetumColumns.MetaKey,
		metaKey,
		model.ForumTopicMetumColumns.TopicID,
		topicId,
	)).Bind(ctx, db, &info)

	logrus.Info(fmt.Sprintf("IncreaseMeta err: %+v", err))

	return nil
}

func (r *customTopicRepository) GetReplyList(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ForumPost, count int64, err error) {
	var page int
	var pageSize int
	var limit int
	var selectCol string
	var mods []qm.QueryMod
	var nextCount int64
	var previousCount int64
	var oldest bool
	var latest bool
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(uint64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(uint64); ok && val > 0 {
				pageSize = int(val)
			}
		case "limit":
			if val, ok := v.(uint32); ok && val > 0 {
				limit = int(val)
			}
		case "offset":
			if withCount == 2 {
				if val, ok := v.(uint32); ok && val > 0 {
					mods = append(mods, qm.Offset(int(val)))
					previousCount = 1
				}
			}
		case "preMinId":
			if withCount == 3 {
				if val, ok := v.(uint64); ok && val > 0 {
					mods = append(mods, qm.OrderBy(model.ForumPostColumns.PostID+" DESC"))
					mods = append(mods, model.ForumPostWhere.PostID.LT(val))
					previousCount = 1
				}
			}
		case "preMaxId":
			if withCount == 4 {
				if val, ok := v.(uint64); ok && val > 0 {
					mods = append(mods, qm.OrderBy(model.ForumPostColumns.PostID+" ASC"))
					mods = append(mods, model.ForumPostWhere.PostID.GT(val))
					previousCount = 1
				}
			}
		case "orderBy":
			if withCount != 3 && withCount != 4 {
				if val, ok := v.(string); ok && val != "" {
					mods = append(mods, qm.OrderBy(val))
				}
			}
		case "select":
			if withCount != 5 {
				if val, ok := v.(string); ok && val != "" {
					selectCol = val
				}
			}
		case "oldest":
			if val, ok := v.(string); ok && val == "1" {
				oldest = true
			}
		case "latest":
			if val, ok := v.(string); ok && val == "1" {
				latest = true
			}
		}
	}

	for key, opMap := range filter {
		switch key {
		case model.ForumPostColumns.PostID:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostID, opMap)...)
		case model.ForumPostColumns.ForumID:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.ForumID, opMap)...)
		case model.ForumPostColumns.TopicID:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.TopicID, opMap)...)
		case model.ForumPostColumns.ParentID:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.ParentID, opMap)...)
		case model.ForumPostColumns.PostAuthorID:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorID, opMap)...)
		case model.ForumPostColumns.PostAuthorNickname:
			mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.PostAuthorNickname, opMap)...)
		case model.ForumPostColumns.PostAuthorRankID:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorRankID, opMap)...)
		case model.ForumPostColumns.PostAuthorRoleID:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorRoleID, opMap)...)
		case model.ForumPostColumns.PostAuthorAnonymous:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostAuthorAnonymous, opMap)...)
		case model.ForumPostColumns.PostStatus:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PostStatus, opMap)...)
		case model.ForumPostColumns.Sticky:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.Sticky, opMap)...)
		case model.ForumPostColumns.CommentStatus:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.CommentStatus, opMap)...)
		case model.ForumPostColumns.CommentCount:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.CommentCount, opMap)...)
		case model.ForumPostColumns.PhotoCount:
			mods = append(mods, modelx.GetIntMods(model.ForumPostColumns.PhotoCount, opMap)...)
		case model.ForumPostColumns.AverageRating:
			mods = append(mods, modelx.GetStringMods(model.ForumPostColumns.AverageRating, opMap)...)
		}
	}

	if oldest || latest {
		if oldest {
			mods = append(mods, qm.Select(fmt.Sprintf(
				"min(%s) as %s",
				model.ForumPostColumns.PostID,
				model.ForumPostColumns.PostID,
			)))
		} else {
			mods = append(mods, qm.Select(fmt.Sprintf(
				"max(%s) as %s",
				model.ForumPostColumns.PostID,
				model.ForumPostColumns.PostID,
			)))
		}
		mods = append(mods, qm.GroupBy(model.ForumPostColumns.TopicID))

		if withCount == 2 && limit > 0 {
			mods = append(mods, qm.Limit(limit+1))
		}

		preList, listErr := model.ForumPosts(mods...).All(ctx, r.mysqlConn)
		if listErr != nil {
			err = listErr
			return
		}

		if len(preList) > 0 {
			if withCount >= 2 {
				if limit > 0 && len(preList) > limit {
					nextCount = 1
					preList = preList[:limit]
				}
				count = previousCount + nextCount*2
			}
			if len(preList) == 0 {
				return
			}

			tids := make([]uint64, 0, len(preList))
			for _, v := range preList {
				tids = append(tids, v.PostID)
			}
			var subMods []qm.QueryMod
			subMods = append(subMods, model.ForumPostWhere.PostID.IN(tids))
			var columns []string
			if selectCol != "" {
				if selectCol != "," {
					columns = strings.Split(selectCol, ",")
				}
				columns = append(columns, model.ForumPostColumns.PostID)
			}
			if len(columns) > 0 {
				subMods = append(subMods, qm.Select(columns...))
			}
			list, err = model.ForumPosts(subMods...).All(ctx, r.mysqlConn)
			return
		}

		return
	}

	if withCount >= 2 {
		if withCount == 5 { // 统计回复次数
			mods = append(mods, qm.Select(fmt.Sprintf(
				"%s, count(%s) as %s",
				model.ForumPostColumns.TopicID,
				model.ForumPostColumns.PostID,
				model.ForumPostColumns.CommentCount,
			)))
			mods = append(mods, qm.GroupBy(model.ForumPostColumns.TopicID))
		}

		if limit > 0 {
			mods = append(mods, qm.Limit(limit+1))
		}
	} else if withCount >= 0 {
		count, err = model.ForumPosts(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}

		if pageSize > 0 {
			if page > 1 {
				mods = append(mods, qm.Offset(pageSize*(page-1)))
			}
			mods = append(mods, qm.Limit(pageSize))
		}
	} else {
		if pageSize > 0 {
			if page > 1 {
				mods = append(mods, qm.Offset(pageSize*(page-1)))
			}
			mods = append(mods, qm.Limit(pageSize))
		}
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		} else {
			columns = append(columns, model.ForumPostColumns.PostID)
		}
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ForumPosts(mods...).All(ctx, r.mysqlConn)
	if withCount >= 2 {
		if limit > 0 && len(list) > limit {
			nextCount = 1
			list = list[:limit]
		}
		count = previousCount + nextCount*2
	}

	return
}
